package com.quectel.business.pay;

import com.google.common.base.Preconditions;
import com.quectel.business.pay.ex.PayHandlerNotFoundException;
import com.quectel.business.pay.handler.BasePayHandler;
import com.quectel.business.pay.handler.model.CreateBusinessResp;
import com.quectel.business.pay.vo.CreateOrderRequest;
import com.quectel.constant.global.SystemConstants;
import com.quectel.constant.sys.SysConfigKeyConstants;
import com.quectel.core.module.merchant.dto.MerchantVillageDto;
import com.quectel.core.module.merchant.service.MerchantVillageService;
import com.quectel.pay.module.merchant.dto.PayChannelDto;
import com.quectel.pay.module.merchant.service.PayChannelService;
import com.quectel.pay.module.order.constants.OrderConstants;
import com.quectel.pay.module.order.dto.PayOrderDto;
import com.quectel.pay.module.order.dto.PayOrderRefundDto;
import com.quectel.pay.module.order.dto.request.InitOrderParams;
import com.quectel.pay.module.order.dto.response.OrderCreatedResponseDto;
import com.quectel.pay.module.order.dto.response.SendRefundResp;
import com.quectel.pay.module.order.service.*;
import com.quectel.sdk.officialaccounts.OfficialAccountsUtils;
import com.quectel.sys.module.sys.service.SysConfigService;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.common.Response;
import com.quectel.util.http.RequestUtils;
import com.quectel.util.spring.SpringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;

/**
 * 订单处理中心
 *
 * @author caolu
 */
@Component
public class PayBusiness {
    @DubboReference
    private PayOrderService payOrderService;
    @DubboReference
    private PayOrderRefundService payOrderRefundService;
    @DubboReference
    private OrderWechatAppPayService orderWechatAppPayService;
    @DubboReference
    private OrderWechatJSAPIPayService orderWechatJSAPIPayService;
    @DubboReference
    private OrderAliAppPayService orderAliAppPayService;
    @DubboReference
    private OrderAliJSAPIPayService orderAliJSAPIPayService;
    @DubboReference
    private PayChannelService payChannelService;
    @DubboReference
    private MerchantVillageService merchantVillageService;
    @DubboReference
    private SysConfigService sysConfigService;

    /**
     * 下单功能
     *
     * @param sourceApi 来源
     * @param req
     * @return
     */
    public Response<OrderCreatedResponseDto> createOrder(SystemConstants.ApiPath sourceApi, CreateOrderRequest req) {

        // 拿到请求对象 如果不是Controller调用就会报错正好实现该方法必须由Controller调用的目的
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();

        // 1.创建业务订单
        Response<CreateBusinessResp> response = createBusinessOrder(req);
        if (!response.isSuccess()) {
            return Response.error(response.getMessage());
        }
        CreateBusinessResp businessCreatedRespData = response.getData();

        // 2.创建通用订单
        MerchantVillageDto merchantVillageDto = queryByVillageAndBusiness(businessCreatedRespData.getVillageId(), req.getBusinessType());
        if (merchantVillageDto == null) {
            // 校验商户配置情况
            createOrderFailedNotify(req);
            return Response.error("收款账户未配置");
        }
        InitOrderParams orderRequest = new InitOrderParams();
        orderRequest.setTenantId(businessCreatedRespData.getTenantId());
        orderRequest.setVillageId(businessCreatedRespData.getVillageId());
        orderRequest.setProductName(businessCreatedRespData.getProductName());
        orderRequest.setBusinessType(req.getBusinessType());
        orderRequest.setBusinessId(businessCreatedRespData.getBusinessId());

        orderRequest.setPayType(req.getPayType());
        orderRequest.setTerminalType(req.getTerminalType());
        orderRequest.setMobileType(req.getMobileType());

        orderRequest.setMobile(req.getMobile());
        orderRequest.setOriginalPrice(businessCreatedRespData.getOriginalPrice());
        orderRequest.setSpecialPrice(businessCreatedRespData.getSpecialPrice());
        orderRequest.setTtl(businessCreatedRespData.getTtl());

        // 设置ip为必须操作
        orderRequest.setIpAddress(RequestUtils.getRealIp(request));


        OrderCreatedResponseDto responseDto = null;
        if (SystemConstants.PayType.ZFB.getType().equals(req.getPayType())
        ) {

            // 查找支付相关配置
            PayChannelDto payChannelDto = payChannelService.selectByMchIdAndPayType(merchantVillageDto.getMerchantId(),
                    SystemConstants.PayConfigType.ZHB.getType());
            if (payChannelDto == null) {
                // 校验商户配置情况
                createOrderFailedNotify(req);
                return Response.error("收款账户未配置");
            }
            orderRequest.setPayChannelId(payChannelDto.getId());
            String gateway = sysConfigService.getConfigValueByKey(SysConfigKeyConstants.GATEWAY_OUTER_BASE_URL);
            orderRequest.setNotifyUrl(
                    gateway + sourceApi.getPath() + "payNotify/completeNotify"
                            + "/" + SystemConstants.PayType.ZFB.getType()
                            + "/" + null
                            + "/" + orderRequest.getTerminalType()
                            + "/" + payChannelDto.getId()

            );
            // 支付宝退款不支持回调
            orderRequest.setRefundNotifyUrl(null);

            if (SystemConstants.TerminalType.JSAPI.getType().equals(req.getTerminalType())) {
                PayChannelDto.AliChannelConfig payConfig =
                        JacksonUtils.parse(payChannelDto.getPayConfig(), PayChannelDto.AliChannelConfig.class);
                //需要获取openId 不然无法支付
                String zhbUserId = OfficialAccountsUtils.getZfbUserId(
                        payConfig.getAppId(),
                        payConfig.getPublicKey(),
                        payConfig.getPrivateKey(),
                        req.getAuthCode()
                );
                orderRequest.setOpenId(zhbUserId);

                responseDto = orderAliJSAPIPayService.createOrder(orderRequest);
            } else if (SystemConstants.TerminalType.APP.getType().equals(req.getTerminalType())) {
                responseDto = orderAliAppPayService.createOrder(orderRequest);
            }
        } else if (SystemConstants.PayType.WX.getType().equals(req.getPayType())) {
            if (SystemConstants.TerminalType.JSAPI.getType().equals(req.getTerminalType())) {
                // 微信H5支付
                PayChannelDto payChannelDto = payChannelService.selectByMchIdAndPayType(merchantVillageDto.getMerchantId(),
                        SystemConstants.PayConfigType.WX_JSAPI.getType());
                if (payChannelDto == null) {
                    // 校验商户配置情况
                    createOrderFailedNotify(req);
                    return Response.error("收款账户未配置");
                }
                orderRequest.setPayChannelId(payChannelDto.getId());
                PayChannelDto.WeChatJSAPIChannelConfig payConfig =
                        JacksonUtils.parse(payChannelDto.getPayConfig(), PayChannelDto.WeChatJSAPIChannelConfig.class);
                // 需要获取openId 不然无法支付
                String wxJsApiUserId = OfficialAccountsUtils.getWxJsApiUserId(
                        payConfig.getAppId(),
                        payConfig.getSecret(),
                        req.getAuthCode()
                );
                orderRequest.setOpenId(wxJsApiUserId);
                String gateway = sysConfigService.getConfigValueByKey(SysConfigKeyConstants.GATEWAY_OUTER_BASE_URL);
//                String gateway = "https://dev-smartcity.quectel.com:10443/ryan/";
                orderRequest.setNotifyUrl(
                        gateway + sourceApi.getPath() + "/payNotify/completeNotify"
                                + "/" + SystemConstants.PayType.WX.getType()
                                + "/" + payConfig.getVersion()
                                + "/" + SystemConstants.TerminalType.JSAPI.getType()
                                + "/" + payChannelDto.getId()

                );
                // 微信退款回调url
                orderRequest.setRefundNotifyUrl(
                        gateway + sourceApi.getPath() + "/payNotify/wxRefundNotify"
                                + "/" + payConfig.getVersion()
                                + "/" + SystemConstants.TerminalType.JSAPI.getType()
                                + "/" + payChannelDto.getId()
                );
                responseDto = orderWechatJSAPIPayService.createOrder(orderRequest);

            } else if (SystemConstants.TerminalType.APP.getType().equals(req.getTerminalType())) {


                // 微信app
                PayChannelDto payChannelDto = payChannelService.selectByMchIdAndPayType(merchantVillageDto.getMerchantId(),
                        SystemConstants.PayConfigType.WX_APP.getType());
                if (payChannelDto == null) {
                    // 校验商户配置情况
                    createOrderFailedNotify(req);
                    return Response.error("收款账户未配置");
                }
                PayChannelDto.WeChatJSAPIChannelConfig payConfig =
                        JacksonUtils.parse(payChannelDto.getPayConfig(), PayChannelDto.WeChatJSAPIChannelConfig.class);

                orderRequest.setPayChannelId(payChannelDto.getId());
                String gateway = sysConfigService.getConfigValueByKey(SysConfigKeyConstants.GATEWAY_OUTER_BASE_URL);
                orderRequest.setNotifyUrl(
                        gateway + "/payNotify/completeNotify"
                                + "/" + SystemConstants.PayType.WX.getType()
                                + "/" + payConfig.getVersion()
                                + "/" + SystemConstants.TerminalType.APP.getType()
                                + "/" + payChannelDto.getId()

                );
                // 微信退款回调url
                orderRequest.setRefundNotifyUrl(
                        gateway + "/payNotify/wxRefundNotify"
                                + "/" + payConfig.getVersion()
                                + "/" + SystemConstants.TerminalType.APP.getType()
                                + "/" + payChannelDto.getId()
                );
                responseDto = orderWechatAppPayService.createOrder(orderRequest);
            }
        }
        // 3.通知业务
        if (responseDto == null || !responseDto.isSuccess()) {
            createOrderFailedNotify(req);
            return Response.error("发起支付出错，请检查请求参数");
        }
        createOrderSuccessNotify(payOrderService.selectByOutTradeNo(responseDto.getOutTradeNo()));
        return Response.<OrderCreatedResponseDto>ok().wrap(responseDto);


    }


    /**
     * 通用创建业务订单
     *
     * @param req
     * @return
     */
    public Response<CreateBusinessResp> createBusinessOrder(CreateOrderRequest req) {
        Preconditions.checkNotNull(req.getBusinessType());
        return searchHandler(req.getBusinessType()).createBusinessOrder(req);
    }

    /**
     * 创建通用订单成功
     *
     * @param payOrder
     * @return
     */
    public Response<Object> createOrderSuccessNotify(PayOrderDto payOrder) {
        Preconditions.checkNotNull(payOrder.getBusinessType());
        return searchHandler(payOrder.getBusinessType()).createOrderSuccessNotify(payOrder);
    }

    /**
     * 创建通用订单失败
     *
     * @param req
     */
    public Response<Object> createOrderFailedNotify(CreateOrderRequest req) {
        return searchHandler(req.getBusinessType()).createOrderFailedNotify(req);
    }

    /**
     * 取消订单
     *
     * @param outTradeNo
     * @return
     */
    public Response<Object> cancelOrder(String outTradeNo) {
        PayOrderDto payOrderDto = payOrderService.selectByOutTradeNo(outTradeNo);
        Preconditions.checkNotNull(payOrderDto);
        if (!OrderConstants.Status.INIT.getStatusValue().equals(payOrderDto.getStatus())) {
            // 非法取消 只有初始化订单才可以取消
            throw new PayHandlerNotFoundException("Illegal cancel");
        }
        payOrderDto.setStatus(OrderConstants.Status.CANCEL.getStatusValue());
        payOrderService.updateById(payOrderDto);
        return searchHandler(payOrderDto.getBusinessType()).cancelOrder(payOrderDto);
    }

    /**
     * 支付成功回调
     *
     * @param payOrder
     * @return
     */
    public Response<Object> paySuccessNotify(PayOrderDto payOrder) {
        Preconditions.checkNotNull(payOrder.getBusinessType());
        return searchHandler(payOrder.getBusinessType()).paySuccessNotify(payOrder);
    }


    /**
     * 发起退款申请 如果发起成功转台会扭转为正在退款 如果正在退款中也可以再发起 正在退款再次发起需要传入退款订单号
     *
     * @param outTradeNo   订单号
     * @param outRefundNo  退款订单号 由调用者传入保证幂等性 PayOrderService.genOrderNo();
     * @param refundAmount
     * @param refundType   @see OrderRefundConstants 1自动结算2人工
     * @param userAccount  操作人 人工退款需要传入
     * @return
     */
    public Response<Object> sendRefundRequest(String outTradeNo,
                                              String outRefundNo,
                                              BigDecimal refundAmount,
                                              Byte refundType,
                                              String userAccount) {


        PayOrderDto payOrderDto = payOrderService.selectByOutTradeNo(outTradeNo);

        Response<Object> response =
                searchHandler(payOrderDto.getBusinessType()).refundPreCheck(payOrderDto, refundAmount);
        if (response == null) {
            return Response.error("不满足退款条件，无法退款");
        }
        if (!response.isSuccess()) {
            return response;
        }


        Preconditions.checkNotNull(payOrderDto);
        SendRefundResp sendRefundResp = payOrderRefundService.sendRefundRequest(
                outTradeNo,
                outRefundNo,
                refundAmount,
                refundType,
                payOrderDto.getRefundNotifyUrl(),
                userAccount
        );
        if (sendRefundResp.isSendSuccess()) {
            if (sendRefundResp.isRefundWithSynchronization()) {
                // 如果是同步方式 需要立即通知业务业务退款成功
                return refundSuccessNotify(payOrderRefundService.selectByOutRefundNo(outRefundNo));
            }
            return Response.ok();
        }
        // 退款成功
        return Response.error(sendRefundResp.getFailedReason());
    }

    /**
     * 退款成功回调
     *
     * @param payOrderRefund
     * @return
     */
    public Response<Object> refundSuccessNotify(PayOrderRefundDto payOrderRefund) {
        Preconditions.checkNotNull(payOrderRefund);
        Preconditions.checkNotNull(payOrderRefund.getPayOrderDto());
        return searchHandler(payOrderRefund.getPayOrderDto().getBusinessType()).refundSuccessNotify(payOrderRefund);
    }

    /**
     * 按照业务类型寻找执行器
     *
     * @param businessType
     * @return
     */
    public BasePayHandler searchHandler(String businessType) {

        try {
            Class<?> aClass =
                    Class.forName(BasePayHandler.class.getPackage().getName() + "." + businessType + "PayHandler");
            if (BasePayHandler.class.isAssignableFrom(aClass)) {
                return (BasePayHandler) SpringUtils.getBean(aClass);
            }
            throw new PayHandlerNotFoundException("The business type of " + businessType + " handler not found.");
        } catch (Exception e) {
            throw new PayHandlerNotFoundException(e);
        }
    }

    /**
     * 查询小区的业务所路由的商户配置
     *
     * @param villageId
     * @param businessType
     * @return
     */
    private MerchantVillageDto queryByVillageAndBusiness(Long villageId, String businessType) {
        return merchantVillageService.queryByVillageAndBusiness(villageId, businessType);
    }

}
